package com.lifang.framework.beans.factory.annotation;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import com.lifang.framework.annotation.beans.Value;
import com.lifang.framework.annotation.beans.Autowired;
import com.lifang.framework.annotation.beans.Qualifier;
import com.lifang.framework.beans.BeansException;
import com.lifang.framework.beans.factory.*;
import com.lifang.framework.beans.factory.config.InstantiationAwareBeanPostProcessor;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;

public class CommonAnnotationBeanPostProcessor implements InstantiationAwareBeanPostProcessor, BeanFactoryAware {

    private ConfigurableListableBeanFactory beanFactory;

    private final Map<String, PropertyValues> cachedPropertyData = new HashMap<>();

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
    }

    @Override
    public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {
        PropertyValues propertyValues = this.cachedPropertyData.get(beanName);
        if (propertyValues == null || propertyValues.needsRefresh(bean.getClass())) {
            propertyValues = buildPropertyValues(bean.getClass());
            this.cachedPropertyData.put(beanName, propertyValues);
        }
        return propertyValues;
    }

    private PropertyValues buildPropertyValues(Class<?> clazz) {
        PropertyValues propertyValues = new PropertyValues(clazz);
        for (Field field : clazz.getDeclaredFields()) {
            if (field.isAnnotationPresent(Resource.class)) {
                isStaticField(field, "@Resource annotation is not supported on static fields");
                Resource resourceAnnotation = field.getAnnotation(Resource.class);
                String name = resourceAnnotation.name();

                Object dependentBean;
                if (StrUtil.isNotBlank(name)) {
                    dependentBean = beanFactory.getBean(name);
                } else {
                    dependentBean = beanFactory.getBean(field.getType());
                }

                propertyValues.addPropertyValue(new PropertyValue(field.getName(), dependentBean));
            } else if (field.isAnnotationPresent(Autowired.class)) {
                //Autowired注解解析
                isStaticField(field, "@Autowired annotation is not supported on static fields");
                Qualifier qualifierAnnotation = field.getAnnotation(Qualifier.class);
                Object dependentBean;
                if (qualifierAnnotation != null && StrUtil.isNotBlank(qualifierAnnotation.value())) {
                    dependentBean = beanFactory.getBean(qualifierAnnotation.value(), field.getType());
                } else {
                    dependentBean = beanFactory.getBean(field.getType());
                }
                propertyValues.addPropertyValue(new PropertyValue(field.getName(), dependentBean));
            } else if (field.isAnnotationPresent(Value.class)) {
                //Value注解解析
                isStaticField(field, "@Value annotation is not supported on static fields");
                Value valueAnnotation = field.getAnnotation(Value.class);
                String value = valueAnnotation.value();
                Assert.notBlank(value, clazz.getName() + " filed: " + field.getName() + " value annotationValue empty");
                Object dependentBean = beanFactory.resolveEmbeddedValue(value);
                if (dependentBean == null) {
                    throw new BeansException(clazz.getName() + " Exception during field initialization，No value for " + value);
                }
                propertyValues.addPropertyValue(new PropertyValue(field.getName(), dependentBean));
            }
        }
        return propertyValues;
    }

    private void isStaticField(Field field, String s) {
        if (Modifier.isStatic(field.getModifiers())) {
            throw new IllegalStateException(s);
        }
    }

}
